package com.nuvsoft.android.nuvgamenet.net;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.CopyOnWriteArrayList;

import android.os.PowerManager.WakeLock;
import android.util.Log;

import com.nuvsoft.android.nuvgamenet.GamePlayer;
import com.nuvsoft.android.nuvgamenet.NuvGameNetApplication;
import com.nuvsoft.android.nuvgamenet.NuvGameNetSignal;

public class TCPHandler {
	// private BuzzerApplication mContext;
	private List<TCPListener> listeners;
	private TCPListener playerInfoListener;
	Thread mainThread;
	private ServerSocket srv;
	private int INCOMING_PORT = 57657;
	private WakeLock wl;
	private CopyOnWriteArrayList<TCPConnection> connections;
	private volatile boolean isListening, isHost, sessionOpen;
	private UUID sessionID;

	public class TCPConnection implements Runnable {
		private Socket s;
		private BufferedReader br;
		private BufferedWriter bw;
		private GamePlayer playerInfo;

		public GamePlayer getPlayerInfo() {
			if (playerInfo == null)
				requestPlayerInfo(this);
			return playerInfo;
		}

		public void setPlayerInfo(GamePlayer playerInfo) {
			this.playerInfo = playerInfo;
		}

		public TCPConnection(Socket s) {
			try {
				this.s = s;
				br = new BufferedReader(new InputStreamReader(this.s
						.getInputStream()), 1024 * 4);
				bw = new BufferedWriter(new OutputStreamWriter(this.s
						.getOutputStream()), 1024 * 4);
			} catch (IOException e) {
				bw = null;
				bw = null;
			}
		}

		public void send(String tag, String[] dataStuff) {
			String data = "";
			if (dataStuff != null) {
				for (int i = 0; i < dataStuff.length; i++) {
					data += dataStuff[i];
					if (i != dataStuff.length - 1)
						data += ",";
				}
			}
			if (sessionID == null) {
				try {
					Log.v(TCPHandler.class.getSimpleName(), "Sending: "
							+ String.format("%s,%s\n", tag, data));
					bw.write(String.format("%s,%s\n", tag, data));
					bw.flush();
				} catch (IOException e) {
					e.printStackTrace();
				}
			} else {
				try {
					Log.v(TCPHandler.class.getSimpleName(), "Sending: "
							+ String.format("%s,%s,%s\n", tag, sessionID
									.toString(), data));
					bw.write(String.format("%s,%s,%s\n", tag, sessionID
							.toString(), data));
					bw.flush();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}

		@Override
		public void run() {
			if (br != null && bw != null) {
				while (isListening) {
					Thread.yield();
					try {
						if (br.ready()) {
							String message = br.readLine();
							Log.v(TCPHandler.class.getSimpleName(),
									"Received: " + message);
							String[] split = message.split(",");
							if (split.length >= 2) {
								if (sessionID != null
										&& split[1].equalsIgnoreCase(sessionID
												.toString())) {
									String[] data = null;
									if (split.length > 2) {
										data = new String[split.length - 2];
										for (int i = 0; i < data.length; i++)
											data[i] = split[i + 2];
									}
									for (TCPListener l : listeners) {
										l.onReceiveVerified(TCPConnection.this,
												split[0], data);
									}
								} else {
									String[] data = null;
									if (split.length > 1) {
										data = new String[split.length - 1];
										for (int i = 0; i < data.length; i++)
											data[i] = split[i + 1];
									}
									for (TCPListener l : listeners) {
										l.onReceiveUnverfied(
												TCPConnection.this, split[0],
												data);
									}
								}
							} else {
								String[] data = null;
								if (split.length > 1) {
									data = new String[split.length - 1];
									for (int i = 0; i < data.length; i++)
										data[i] = split[i + 1];
								}
								for (TCPListener l : listeners) {
									l.onReceiveUnverfied(TCPConnection.this,
											split[0], data);
								}
							}
						}
					} catch (IOException e) {
						e.printStackTrace();
					}
				}
			}
		}

		public String getAddress() {
			return s.getInetAddress().getHostAddress();
		}
	}

	public TCPHandler() {
		listeners = new CopyOnWriteArrayList<TCPListener>();
		connections = new CopyOnWriteArrayList<TCPConnection>();
		setHost(false);
		sessionOpen = false;
		isListening = false;
		try {
			srv = new ServerSocket(INCOMING_PORT);
			srv.setSoTimeout(100);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	public void setHost(boolean isHost) {
		this.isHost = isHost;
		if (isHost)
			sessionID = UUID.randomUUID();
	}

	public void setSessionOpen(boolean sessionOpen) {
		if (isHost())
			this.sessionOpen = sessionOpen;
		else
			this.sessionOpen = false;
	}

	public synchronized void addTCPListener(TCPListener l) {
		synchronized (listeners) {
			if (!listeners.contains(l))
				listeners.add(l);
		}
	}

	public synchronized void stopListening() {
		wl.release();
		isListening = false;
		setHost(false);
		sessionOpen = false;
		mainThread = null;
		connections.clear();
	}

	boolean verifySession(String sid) {
		return sessionID.toString().equalsIgnoreCase(sid);
	}

	public synchronized void startListening() {
		isListening = true;
		Runnable r = new Runnable() {
			public synchronized void run() {
				while (isListening) {
					Thread.yield();
					try {
						Socket receiveSocket = srv.accept();
						TCPConnection newListener = new TCPConnection(
								receiveSocket);
						new Thread(newListener).start();
						Log.v(TCPHandler.class.getSimpleName(),
								"NEW CONNECTION: "
										+ receiveSocket.getInetAddress()
												.getHostAddress());
						getConnections().add(newListener);
					} catch (IOException e) {
						// timeout or other error
						continue;
					}
				}
			}
		};
		mainThread = new Thread(r);
		mainThread.start();

		playerInfoListener = new TCPListener() {
			@Override
			public void onReceiveUnverfied(TCPConnection t, String tag,
					String[] data) {
				if (tag.equalsIgnoreCase(NuvGameNetSignal.REQEST_SESSION_ID
						.name())) {
					if (isHost() && sessionOpen) {
						t.send(NuvGameNetSignal.SESSION_ID.name(),
								new String[] { sessionID.toString() });

						if (t.getPlayerInfo() == null) {
							requestPlayerInfo(t);
						}
					}
				} else if (tag.equalsIgnoreCase(NuvGameNetSignal.SESSION_ID
						.name())) {
					sessionID = UUID.fromString(data[0]);
				}
			}

			@Override
			public void onReceiveVerified(TCPConnection t, String tag,
					String[] data) {
				if (tag.equalsIgnoreCase(NuvGameNetSignal.PLAYER_INFO.name())) {
					t.setPlayerInfo(new GamePlayer(data[0]));
				} else if (tag.equalsIgnoreCase(NuvGameNetSignal.REQUEST_INFO
						.name())) {
					t.send(NuvGameNetSignal.PLAYER_INFO.name(),
							new String[] { NuvGameNetApplication.getNick() });
				}
			}
		};
		addTCPListener(playerInfoListener);
	}

	public synchronized void removeBroadcastListener(MulticastListener l) {
		synchronized (listeners) {
			listeners.remove(l);
		}
	}

	private void requestPlayerInfo(final TCPConnection t) {
		if (sessionID == null) {
			t.send(NuvGameNetSignal.REQEST_SESSION_ID.name(), null);
			new Thread(new Runnable() {
				@Override
				public void run() {
					while (sessionID == null) {
						try {
							Thread.sleep(1000);
						} catch (InterruptedException e) {
							e.printStackTrace();
						}
					}
					t.send(NuvGameNetSignal.REQUEST_INFO.name(), null);
				}
			}).start();
		} else {
			t.send(NuvGameNetSignal.REQUEST_INFO.name(), null);
		}
	}

	public TCPConnection connect(String address) {
		for (TCPConnection t : connections) {
			if (t.s.getInetAddress().getHostAddress().equalsIgnoreCase(address)) {
				// already connected
				return null;
			}
		}
		try {
			Socket s = new Socket(address, INCOMING_PORT);
			if (s.isConnected()) {
				TCPConnection newListener = new TCPConnection(s);
				new Thread(newListener).start();
				connections.add(newListener);
				requestPlayerInfo(newListener);
				return newListener;
			} else {
				s.close();
				return null;
			}
		} catch (UnknownHostException e) {
			e.printStackTrace();
			return null;
		} catch (IOException e) {
			e.printStackTrace();
			return null;
		}
	}

	public boolean isSessionOpen() {
		return this.sessionOpen;
	}

	public CopyOnWriteArrayList<TCPConnection> getConnections() {
		return connections;
	}

	public boolean isHost() {
		return isHost;
	}

	public void sendToAll(String tag, String[] data) {
		for (TCPConnection t : connections) {
			t.send(tag, data);
		}
	}
}
